package flinty.autocomplete.combobox;

import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

/**
 * successor of BaseAutoCompleteComboBoxModel which is used when a list of items
 * is very long and only few items are shown at popup. In this case we add a
 * "more" item and when user select it the method selectObject of
 * SelectObjectCallback is executed which for example display a dialog where
 * user select an item
 * 
 * @param <T>
 *            is a class of items displayed in combobox
 */
public class ShortenedListComboBoxModel<T> extends
		BaseAutoCompleteComboBoxModel<T> {

	private static final long serialVersionUID = -8856773607277716058L;

	private static Logger logger = AutoCompleteComboBoxDecorator
			.createLogger(ShortenedListComboBoxModel.class);

	private final DataProvider<T> provider;

	private final StringConverter<T> renderer;

	private final List<ItemWrapper<T>> data = new LinkedList<ItemWrapper<T>>();

	private final List<ItemWrapper<T>> preprocessed = new LinkedList<ItemWrapper<T>>();

	private final List<ItemWrapper<T>> shortenedList = new LinkedList<ItemWrapper<T>>();

	private final ItemWrapper<T> moreItem;

	private final SelectObjectCallback<T> callback;

	/**
	 * 
	 * @param prov
	 *            provider of data that is to be displayed in combobox
	 * @param rend
	 *            converter that represent item of class T as a String
	 * @param sl
	 *            shortened list of items, for example most frequently chosen
	 *            items
	 * @param clb
	 *            callback providing an action when user selects "more" item
	 * @param moreText
	 *            the string representation of "more" item
	 */
	public ShortenedListComboBoxModel(DataProvider<T> prov,
			StringConverter<T> rend, List<T> sl, SelectObjectCallback<T> clb,
			String moreText) {
		provider = prov;
		renderer = rend;
		for (T item : sl) {
			shortenedList.add(new ItemWrapper<T>(item, item == null ? null
					: renderer.render(item)));
		}
		moreItem = new ItemWrapper<T>(null, moreText);
		shortenedList.add(moreItem);
		callback = clb;
	}

	@Override
	protected List<ItemWrapper<T>> getFilteredData() {
		return data;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void setSelectedItem(Object anObject) {
		logger.fine("selecting item " + anObject);
		if (anObject == null || !(anObject instanceof ItemWrapper<?>)) {
			super.setSelectedItem(new ItemWrapper<T>((T) anObject,
					anObject == null ? null : renderer.render((T) anObject)));
			return;
		}
		if (anObject == moreItem) {
			Object lastSelected = getLastSelected();
			ValueHolder<Boolean> result = new ValueHolder<Boolean>(
					Boolean.FALSE);
			ValueHolder<T> item = new ValueHolder<T>();
			callback.selectObject(result, item);
			super.setSelectedItem(Boolean.TRUE.equals(result.getValue()) ? new ItemWrapper<T>(
					item.getValue(), item.getValue() == null ? null : renderer
							.render(item.getValue())) : lastSelected);
			getDocument().setText(
					getSelectedItem() != null ? getSelectedItem().toString()
							: null);
			return;

		}
		super.setSelectedItem(anObject);
	}

	@Override
	protected void loadData() {
		data.clear();
		data.addAll(shortenedList);
	}

	@Override
	protected boolean preprocessFiltering(String pattern) {
		if (pattern == null || pattern.trim().length() == 0) {
			preprocessed.clear();
			preprocessed.addAll(shortenedList);
			return true;
		}
		preprocessed.clear();
		List<T> lst = provider.getData(pattern);
		for (int n = 0; n < lst.size(); n++) {
			if (n >= shortenedList.size() - 1) {
				preprocessed.add(moreItem);
				break;
			}
			T item = lst.get(n);
			preprocessed.add(new ItemWrapper<T>(item, item == null ? null
					: renderer.render(item)));
		}
		return preprocessed.size() > 0;
	}

	@Override
	protected void updateFilteredData() {
		data.clear();
		data.addAll(preprocessed);
	}

	@Override
	protected void resetFilteredData() {
		data.clear();
		data.addAll(shortenedList);
	}

	@Override
	protected boolean isValid(ItemWrapper<T> anObject) {
		return true;
	}

	/**
	 * callback providing an action when user selects "more" item
	 * 
	 * @param <T>
	 *            is a class of items displayed in combobox
	 */
	public static interface SelectObjectCallback<T> {
		/**
		 * 
		 * @param result
		 *            stores the result of dialog, TRUE if user pressed OK,
		 *            FALSE otherwise
		 * @param item
		 *            stores the item that user choose at dialog
		 */
		void selectObject(ValueHolder<Boolean> result, ValueHolder<T> item);
	}
}
